package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author llt
 * @since 2024-08-06
 */
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService couponScopeService;

    private final IExchangeCodeService exchangeCodeService;

    private final IUserCouponService userCouponService;

    @Transactional
    @Override
    public void saveCoupon(CouponFormDTO dto) {
        // 1. dto 转 po 保存优惠券
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        this.save(coupon);

        // 2. 判断是否限定范围（false-未限定）
        if (!dto.getSpecific()) {
            return;
        }

        // 3. 校验 dto.scopes
        List<Long> scopes = dto.getScopes();
        if (CollUtils.isEmpty(scopes)) {
            throw new BizIllegalException("作用范围不能为空");
        }

        // 4. 保存优惠卷限定范围
        List<CouponScope> csList = scopes
                .stream().map(aLong -> new CouponScope().setCouponId(coupon.getId()).setBizId(aLong).setType(1))
                .collect(Collectors.toList());

        couponScopeService.saveBatch(csList);
    }

    @Override
    public PageDTO<CouponPageVO> queryCouponPage(CouponQuery query) {
        // 1. 分页条件查询优惠券表
        Page<Coupon> page = this.lambdaQuery()
                .eq(query.getType() != null, Coupon::getDiscountType, query.getType())
                .eq(query.getStatus() != null, Coupon::getStatus, query.getStatus())
                .like(StringUtils.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = page.getRecords();

        // 2. 封装 vo 返回
        List<CouponPageVO> voList = BeanUtils.copyList(records, CouponPageVO.class);

        return PageDTO.of(page, voList);
    }

    @Override
    public void issueCoupon(Long id, CouponIssueFormDTO dto) {
        // 1. 校验优惠券 id 是否存在
        Coupon coupon = this.getById(id);

        // 2. 校验优惠卷状态（待发放和暂停的才能发放）
        if (coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != CouponStatus.PAUSE) {
            throw new BadRequestException("只有待发放和暂停的才能发放");
        }

        // 优惠卷是否立刻发放
        LocalDateTime now = LocalDateTime.now();
        boolean isBeginIssue = dto.getIssueBeginTime() == null || !dto.getIssueBeginTime().isAfter(now);

        // 3. 修改优惠卷 可以领取范围时间, 有效时间, 状态, 天数
        Coupon tmp = BeanUtils.copyBean(dto, Coupon.class);
        if (isBeginIssue) {
            tmp.setStatus(CouponStatus.ISSUING);
            tmp.setIssueBeginTime(now);
        }else {
            tmp.setStatus(CouponStatus.UN_ISSUE);
        }

        this.updateById(tmp);

        // 4. 如果优惠券的领取方式是 指定发放 && 状态为待发放, 需要生成兑换码
        if (coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT) {
            coupon.setIssueEndTime(tmp.getIssueEndTime());
            exchangeCodeService.ayncGenerateExchangeCode(coupon);
        }
    }

    @Override
    public List<CouponVO> queryIssuingCoupons() {
        // 1. 查询优惠卷表 (发放中 且 手动领取)
        List<Coupon> list = this.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if(CollUtils.isEmpty(list)) {
            return CollUtils.emptyList();
        }

        // 2. 查询用户卷表 (当前用户, 发放中的优惠券id)
        Set<Long> cids = list.stream().map(Coupon::getId).collect(Collectors.toSet());// 正在发放的优惠卷id集合
        // 当前用户针对正在发放的优惠卷领取记录
        List<UserCoupon> record = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, cids)
                .list();
        // 2.1 统计当前用户 针对每一个卷已领取的数量
        Map<Long, Long> issueMap = record.stream()
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        // 2.2 统计当前用户 针对每一个卷已领取且未使用的数量
        Map<Long, Long> unuseMap = record.stream()
                .filter(c -> c.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));

        // 2. 返回
        List<CouponVO> voList = new ArrayList<>();
        for (Coupon coupon : list) {
            CouponVO vo = BeanUtils.copyBean(coupon, CouponVO.class);
            // 是否可以领取
            Long issNum = issueMap.getOrDefault(coupon.getId(), 0L); // 已经领的数量
            boolean avaliable = coupon.getIssueNum() < coupon.getTotalNum() && issNum.intValue() < coupon.getUserLimit();
            vo.setAvailable(avaliable);
            // 是否可以使用
            boolean received = unuseMap.getOrDefault(coupon.getId(), 0L) > 0;
            vo.setReceived(received);
            voList.add(vo);
        }

        return voList;
    }


}
